package com.eduagent.xwqeduagent.service.lmpl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.alibaba.cloud.ai.dashscope.chat.MessageFormat;
import com.eduagent.xwqeduagent.adviser.MyLoggerAdvisor;
import com.eduagent.xwqeduagent.app.RoutingStudentAgent.prompt.textAgentSystemPrompt;
import com.eduagent.xwqeduagent.config.ToolResultHolder;
import com.eduagent.xwqeduagent.constant.Constant;
import com.eduagent.xwqeduagent.constant.prompt.CourseReference;
import com.eduagent.xwqeduagent.model.entity.CourseOutline;
import com.eduagent.xwqeduagent.model.enums.ChatEventTypeEnum;
import com.eduagent.xwqeduagent.model.vo.ChatEventVO;
import com.eduagent.xwqeduagent.openFeign.client.GetInfoClient;
import com.eduagent.xwqeduagent.openFeign.client.AdminClient;
import com.eduagent.xwqeduagent.openFeign.entity.UserAndRole;
import com.eduagent.xwqeduagent.openFeign.result.Result;
import com.eduagent.xwqeduagent.service.ChatService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.Media;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import static com.eduagent.xwqeduagent.constant.prompt.StuTalkSystemPrompt.SYSTEM_PROMPT;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;

@Slf4j
@Service
@RequiredArgsConstructor
public class ChatServiceImpl implements ChatService {

    private final ChatClient chatClient;

    private final String stuTalkSystemPrompt = SYSTEM_PROMPT;

    // 存储大模型的生成状态，这里采用ConcurrentHashMap是确保线程安全
    // 目前的版本暂时用Map实现，如果考虑分布式环境的话，可以考虑用redis来实现
    private static final Map<String, Boolean> GENERATE_STATUS = new ConcurrentHashMap<>();

    private final ChatMemory chatMemory;

    private final ToolCallback[] studentAllTools;

    private final AdminClient adminClient;

    private final textAgentSystemPrompt textAgentSystem;

    // 输出结束的标记
    private static final ChatEventVO STOP_EVENT = ChatEventVO.builder().eventType(ChatEventTypeEnum.STOP.getValue()).build();

    @Override
    public Flux<ChatEventVO> chat(String question, String sessionId) {

        // 获取对话id
        var conversationId = ChatService.getConversationId(sessionId);
        // 大模型输出内容的缓存器，用于在输出中断后的数据存储
        StringBuilder outputBuilder = new StringBuilder();

        // 生成请求id
        var requestId = IdUtil.fastSimpleUUID();

        //获取当前登录学生的信息
        Result<UserAndRole> result = adminClient.getById();
        if (result == null || result.getData() == null) {
            throw new RuntimeException("获取用户信息失败");
        }
        UserAndRole userAndRole = result.getData();
        String userId = userAndRole.getId();

        return this.chatClient.prompt()
                .system(promptSystem -> promptSystem
                        .text(stuTalkSystemPrompt) // 设置系统提示语
                        .param("now", DateUtil.now()) // 设置当前时间的参数
                )
                .advisors(advisor -> advisor.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId))
                .toolContext(MapUtil.<String, Object>builder() // 设置tool列表
                        .put(Constant.REQUEST_ID, requestId) // 设置请求id参数
                        .build()
                )
                .user(question)
                .tools(studentAllTools)
                .stream()
                .chatResponse()
                .doFirst(() -> {  //输出开始，标记正在输出
                    GENERATE_STATUS.put(sessionId, true);
                })
                .doOnComplete(() -> { //输出结束，清除标记
                    GENERATE_STATUS.remove(sessionId);
                })
                .doOnError(throwable -> GENERATE_STATUS.remove(sessionId)) // 错误时清除标记
                .doOnCancel(() -> {
                    // 当输出被取消时，保存输出的内容到历史记录中
                    this.saveStopHistoryRecord(conversationId, outputBuilder.toString());
                })
                // 输出过程中，判断是否正在输出，如果正在输出，则继续输出，否则结束输出
                .takeWhile(s -> Optional.ofNullable(GENERATE_STATUS.get(sessionId)).orElse(false))
                .map(chatResponse -> {
                    // 对于响应结果进行处理，如果是最后一条数据，就把此次消息id放到内存中
                    // 主要用于存储消息数据到 redis中，可以根据消息di获取的请求id，再通过请求id就可以获取到参数列表了
                    // 从而解决，在历史聊天记录中没有外参数的问题
                    var finishReason = chatResponse.getResult().getMetadata().getFinishReason();
                    if (StrUtil.equals(Constant.STOP, finishReason)) {
                        // 2. 正确地从 ChatResponse 的元数据中取 ID
                        String messageId = chatResponse.getMetadata().getId();
                        ToolResultHolder.put(messageId, Constant.REQUEST_ID, requestId);
                    }
                    // 获取大模型的输出的内容
                    String text = chatResponse.getResult().getOutput().getText();
                    // 追加到输出内容中
                    outputBuilder.append(text);
                    // 封装响应对象
                    return ChatEventVO.builder()
                            .eventData(text)
                            .eventType(ChatEventTypeEnum.DATA.getValue())
                            .build();
                })
                .concatWith(Flux.defer(() -> {
                    // 通过请求id获取到参数列表，如果不为空，就将其追加到返回结果中
                    var map = ToolResultHolder.get(requestId);
                    if (CollUtil.isNotEmpty(map)) {
                        ToolResultHolder.remove(requestId); // 清除参数列表

                        // 响应给前端的参数数据
                        ChatEventVO chatEventVO = ChatEventVO.builder()
                                .eventData(map)
                                .eventType(ChatEventTypeEnum.PARAM.getValue())
                                .build();
                        return Flux.just(chatEventVO, STOP_EVENT);
                    }
                    return Flux.just(STOP_EVENT);
                }));
    }

    @Override
    public void stop(String sessionId) {
        // 移除标记
        GENERATE_STATUS.remove(sessionId);
    }

    /**
     * 保存停止输出的记录
     *
     * @param conversationId 会话id
     * @param content        大模型输出的内容
     */
    private void saveStopHistoryRecord(String conversationId, String content) {
        this.chatMemory.add(conversationId, new AssistantMessage(content));
    }


    /**
     * 处理多模型流式聊天请求并返回响应流
     *
     * @param question  用户输入的问题
     * @param sessionId 会话唯一标识符
     * @param files     上传的多媒体文件列表（当前仅支持JPEG图片）
     * @return 包含流式响应内容的Flux对象，每个元素为文本片段，流结束时附加"[END]"标记
     */
    @Override
    public Flux<String> chatStreamMultiModel(String question, String sessionId, List<MultipartFile> files) {
        // 将上传的文件转换为媒体对象列表（仅处理非空文件）
        List<Media> mediaList = new ArrayList<>();
        if (CollUtil.isNotEmpty(files)) {
            for (MultipartFile file : files) {
                var media = new Media(MimeTypeUtils.IMAGE_JPEG, file.getResource());
                mediaList.add(media);
            }
        }

        // 创建包含媒体信息的用户消息对象并设置消息格式类型
        var userMessage = new UserMessage(question, mediaList);
        userMessage.getMetadata().put(DashScopeChatModel.MESSAGE_FORMAT, MessageFormat.IMAGE);

        // 构建多模型聊天请求参数（使用qwen-omni-turbo模型）
        var prompt = new Prompt(userMessage, DashScopeChatOptions.builder()
                .withModel("qwen-omni-turbo")
                .withMultiModel(true)
                .build());

        // 调用聊天客户端生成流式响应内容
        return this.chatClient.prompt(prompt)
                .system(p -> p.param("now", DateUtil.now())) // 设置系统角色参数（当前时间）
                // 设置会话记忆参数（绑定会话ID）
                .advisors(advisor -> advisor.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId))
                .stream()
                .content()
                // 在流结束时添加结束标记
                .concatWith(Flux.just("[END]"));
    }

    @Override
    public String chatText(String question,  String sessionId) {
        ChatClient.CallResponseSpec responseSpec = chatClient
                .prompt()
                .system(textAgentSystemPrompt.SYSTEM_PROMPT)
                .user(question)
                .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 20))
                // 开启日志，便于观察效果
                .advisors(new MyLoggerAdvisor())
                .call();
        return responseSpec.chatResponse().getResult().getOutput().getText();
    }
}
